/***************************************************************************\
 * The Mattise Kernel														*
 * Copyright 2007 Matthew Iselin											*
 * THIS FILE is Public Domain												*
 *																			*
 * fs.cc																	*
 *																			*
 * Handles all filesystem installations and controls any reads/writes		*
 *																			*
\***************************************************************************/

// Includes
#include <system.h>
#include <filesys.h>
#include <LinkedList.hpp>
#include <stdio.h>
#include <string.h>

// TODO: fix this so that more than one filesystem can exist at once

// OK, when a filesystem driver runs it's 'init' or whatever it calls RegisterFileSystem,
// allowing it to fill in its functions...
// When someone calls open() it checks to see if the call is a valid device (ie. open( "dr0" );), if so
// it does typical setup. If it isn't (and "/dev/dr0" wouldn't be valid) it calls filesys_open, which then
// figures out what to do. Its job is to parse the path first and find out if it can get away with only returning
// a device descriptor, and if so it acts as glue between the user and low-level calls.
// Otherwise, it then finds out which drive it needs to work with, detects which filesystem is on that drive
// and then creates a new file descriptor which is stored in a linked list on the kernel heap (TODO: fix that).
// On calls, the file descriptor is used to find out information about the device, then the call is made with an extra
// argument which holds implementation-specific information given by an 'open' call (for instance, FAT might use that space
// for data about the file which the descriptor references.

// In the meantime, while this code is being developed, no filesystem access is going to work and the kernel is not likely
// to compile.

// Types

/// A filesystem for a drive
struct FileSystem {

	// Details
	char id[64];	/** helps in debugging							**/
	int device;		/** device for which this filesystem applies	**/
					/** this is a device id, not a descriptor		**/
	int usrblocksz;	/** size of the block which the filesystem can	**/
					/** use											**/
	
	// Functions
	struct FuncBlock myFunctions;	/** kernel-space functions that handle common tasks **/
	
} __attribute((packed));

/// A filesystem descriptor
struct FileSystemDesc {
	
	/// The host file system information
	struct FileSystem hostFS;
	
	/// A pointer to the user block
	void* usrblock;
	
	/// Device?
	int isdev;
	
	/// If it is a device, this holds the descriptor for it. If not, it holds the drive number (ie. dr[devid])
	int devid;
	
	// The current offset (if device)
	int curroffset;
	
};

/// The filesystem list
CLinkedList<struct FileSystem> fileSystems;

/// The filesystem descriptor list
CLinkedList<struct FileSystemDesc> fileDescriptors;

// Functions

/// Gets a descriptor's current offset
int GetDescriptorOffset( int desc )
{
	return fileDescriptors.Get( desc ).curroffset;
}

/// Parses a directory string into the drive, the path and the filename
void filesys_ParsePath( char* str, char* drive, char* path, char* filename )
{
	// get the length of the string
	int len = strlen( str );
	
	// find the first slash
	int firstslash = find( str, '/', 0, len );
	
	// put this into the drive part
	substr( drive, str, 0, 3 );
	drive[4] = 0;
	
	// find the last slash
	int ls = 0, s = 0;
	while( s != -1 )
	{
		ls = s;
		s = find( str, '/', ls + 1, len );
	}
	
	// put into the path
	substr( path, str, firstslash + 1, ls );
	path[ls-firstslash] = 0;
	
	// get the filename
	substr( filename, str, ls + 1, len );
	filename[ ( len ) - ( ls + 1 ) ] = 0;
}


/// filesys_open: opens a file (if the device is null and the path is dev, then it'll call open for you) - returns a descriptor for the file
int filesys_open( const char* file, const char* mode )
{
	// is it a device?
	if( strcmp( file, "/dev/", strlen( "/dev/" ) ) )
	{		
		// get the device name
		char devname[16];
		memset( devname, 0, 16 );
		substr( devname, file, strlen( "/dev/" ), strlen( file ) );
		
		// open the device
		int devdesc = open( devname, READWRITE );
		
		// create a descriptor for it
		struct FileSystemDesc myDesc;
		myDesc.isdev = 1;
		myDesc.devid = devdesc;
		myDesc.curroffset = 0;
		
		// push it onto the list
		return devdesc;
	}
	else
	{
		// it's a file, so find out what drive we're on
		
		// buffer for the drive information
		char* driveb = (char*) kmalloc( 32 );
		
		// get the drive id
		substr( driveb, file, strlen( "dr" ), find( file, '/', 0, strlen( file ) ) );
		
		// convert it into a number
		int devnum = atoi( driveb );
		
		// free the buffer
		kfree( driveb );
		
		// found/not found
		bool found = false;
		int i;
		
		// search the filesystem list for a match
		for( i = 0; i < fileSystems.Size(); i++ )
		{
			// check
			if( devnum == fileSystems.Get(i).device )
			{				
				// we found a match
				found = true;
				
				// and then break free
				break;
			}
		}
		
		// no match? if so, then we just die
		if( !found )
			return INVALID_FILE;
		
		// otherwise, setup a new descriptor for it
		struct FileSystemDesc myDesc;
		myDesc.devid = devnum;
		myDesc.usrblock = kmalloc( fileSystems.Get(i).usrblocksz );
		myDesc.hostFS = fileSystems.Get(i);
		myDesc.isdev = 0;
		
		// call the filesystem's open
		fileSystems.Get(i).myFunctions.open( devnum, file, mode, myDesc.usrblock );
		
		// push the descriptor
		return fileDescriptors.Push( myDesc );
	}

	// invalid
	return INVALID_FILE;
}

/// filesys_read: reads bytes from a file
int filesys_read( int desc, char* buff, int buffsz, int cnt )
{
	// valid descriptor?
	if( desc != -1 )
	{
		// is it a device?
		if( fileDescriptors.Get( desc ).isdev )
		{
			// read the data
			lseek( fileDescriptors.Get( desc ).devid, fileDescriptors.Get( desc ).curroffset, SEEK_SET );
			return read( fileDescriptors.Get( desc ).devid, buff, buffsz * cnt );
		}
		else
		{
			// read the data
			return fileDescriptors.Get( desc ).hostFS.myFunctions.read( desc, buff, buffsz, cnt, fileDescriptors.Get( desc ).usrblock );
		}
	}
	
	// nothing was read
	return NOBYTESREAD;
}

/// filesys_write: writes bytes to a file
int filesys_write( int desc, char* buff, int buffsz, int cnt )
{
	// valid descriptor?
	if( desc != -1 )
	{
		// is it a device?
		if( fileDescriptors.Get( desc ).isdev )
		{
			// write it
			lseek( fileDescriptors.Get( desc ).devid, fileDescriptors.Get( desc ).curroffset, SEEK_SET );
			return write( fileDescriptors.Get( desc ).devid, buff, buffsz * cnt );
		}
		else
		{
			// write it
			return fileDescriptors.Get( desc ).hostFS.myFunctions.write( desc, buff, buffsz, cnt, fileDescriptors.Get( desc ).usrblock );
		}
	}
	
	// nothing written
	return NOBYTESWRITTEN;
}

/// filesys_seek: seeks to a new location within the file
int filesys_seek( int desc, int newloc, int whence )
{
	// valid descriptor?
	if( desc != -1 )
	{
		// seek
		switch( whence )
		{
			case SEEK_SET:
			
				{
				// set it
				struct FileSystemDesc myDesc = fileDescriptors.Get( desc );
				myDesc.curroffset = newloc;
				fileDescriptors.Insert( myDesc, desc );
				fileDescriptors.Delete( desc );
				}
				
				break;
			
			case SEEK_END:
			
				// invalid
				return -1;
				break;
			
			case SEEK_CUR:
			
				{
				// increment it
				struct FileSystemDesc myDesc = fileDescriptors.Get( desc );
				myDesc.curroffset += newloc;
				fileDescriptors.Insert( myDesc, desc );
				fileDescriptors.Delete( desc );
				}
				
				break;
		}
	}
	
	// seek?
	return newloc;
}

/// filesys_getc: gets a character from the file
int filesys_getc( int desc )
{
	// valid descriptor?
	if( desc != -1 )
	{
		// is it a device?
		if( fileDescriptors.Get( desc ).isdev )
		{
			// read it
			return getc( fileDescriptors.Get( desc ).devid );
		}
		else
		{
			// read it
			fileDescriptors.Get( desc ).hostFS.myFunctions.getc( desc, fileDescriptors.Get( desc ).usrblock );
		}
	}
	
	// nothing
	return 0;
}

/// filesys_putc: puts a character into the file
int filesys_putc( int desc, int c )
{
	// valid descriptor?
	if( desc != -1 )
	{
		// is it a device?
		if( fileDescriptors.Get( desc ).isdev )
		{
			// put the character
			return putc( fileDescriptors.Get( desc ).devid, c );
		}
		else
		{
			// put the character
			return fileDescriptors.Get( desc ).hostFS.myFunctions.putc( desc, c, fileDescriptors.Get( desc ).usrblock );
		}
	}
	
	// nothing
	return -1;
}

/// filesys_eof: are we at EOF?
int filesys_eof( int desc )
{
	// valid descriptor?
	if( desc != -1 )
	{
		// is it a device?
		if( fileDescriptors.Get( desc ).isdev )
		{
			// nothing to do :D
			return 0;
		}
		else
		{
			// read the data
			return fileDescriptors.Get( desc ).hostFS.myFunctions.iseof( desc, fileDescriptors.Get( desc ).usrblock );
		}
	}
	return 0;
}

/// filesys_close: closes the file
int filesys_close( int desc )
{
	// valid descriptor?
	if( desc != -1 )
	{
		// is it a device?
		if( fileDescriptors.Get( desc ).isdev )
		{
			// close it
			return close( fileDescriptors.Get( desc ).devid );
		}
		else
		{
			// read the data
			return fileDescriptors.Get( desc ).hostFS.myFunctions.close( desc, fileDescriptors.Get( desc ).usrblock );
		}
	}
	
	// delete the descriptor
	fileDescriptors.Delete( desc );
	
	// all done
	return 0;
}

/// filesys_filesize: gets the size of a file
int filesys_filesize( int desc )
{
	// valid descriptor?
	if( desc != -1 )
	{
		// is it a device?
		if( fileDescriptors.Get( desc ).isdev )
		{
			// nothing to do :D
			return 0;
		}
		else
		{
			// read the data
			return fileDescriptors.Get( desc ).hostFS.myFunctions.filesize( desc, fileDescriptors.Get( desc ).usrblock );
		}
	}
	return 0;
}

/// RegisterFileSystem: registers a filesystem for a device
int RegisterFileSystem( const char* nm, int usrdata_size, int device, struct FuncBlock* myFunctions )
{	
	// create the file system
	struct FileSystem myFs;
	strcpy( myFs.id, nm );
	myFs.device = device; // this is a device id
	myFs.usrblocksz = usrdata_size;
	myFs.myFunctions = *myFunctions;
	
	// push it onto the list
	return fileSystems.Push( myFs );
}

/// Glue for stdio functions (still not POSIX though): fopen
int fopen( const char* file, const char* mode )
{	
	return filesys_open( file, mode );
}

/// Glue for stdio functions (still not POSIX though): fclose
int fclose( int fd )
{
	return filesys_close( fd );
}

/// Glue for stdio functions (still not POSIX though): fread
int fread( void* buff, int buffsz, int cnt, int fd )
{
	return filesys_read( fd, (char*) buff, buffsz, cnt );
}

/// Glue for stdio functions (still not POSIX though): fwrite
int fwrite( void* buff, int buffsz, int cnt, int fd )
{
	return filesys_write( fd, (char*) buff, buffsz, cnt );
}

/// Glue for stdio functions (still not POSIX though): fputc
int fputc( int fd, int c )
{
	return filesys_putc( fd, c );
}

/// Glue for stdio functions (still not POSIX though): fgetc
int fgetc( int fd )
{
	return filesys_getc( fd );
}

/// Glue for stdio functions (still not POSIX though): iseof
int feof( int fd )
{
	return filesys_eof( fd );
}

/// Glue for stdio functions (still not POSIX though): fsize
int fsize( int fd )
{
	return filesys_filesize( fd );
}

/// Glue for stdio functions (still not POSIX though): fseek
int fseek( int fd, int offset, int whence )
{
	return filesys_seek( fd, offset, whence );
}
